<?
include_once(CONFIG_DOCUMENT_ROOT.'includes/lib/core.class.php');

class Model extends Core {

	static protected $instancia = null;
	private $limit = 20;
	private $offset = 1;
	private $filters = array();
	private $order = array();
	private $groups = array();
	private $where = null;
	private $pk = null;
	private $default_field_types = array(
		'CHAR' => 'text',
		'VARCHAR' => 'text',
		'TINYTEXT' => 'text',
		'TEXT' => 'textarea',
		'BLOB' => 'textarea',
		'MEDIUMTEXT' => 'textarea',
		'MEDIUMBLOB' => 'textarea',
		'LONGTEXT' => 'textarea',
		'LONGBLOB' => 'textarea',
		'TINYINT' => 'text',
		'SMALLINT' => 'text',
		'MEDIUMINT' => 'text',
		'INT' => 'text',
		'BIGINT' => 'text',
		'FLOAT' => 'text',
		'DOUBLE' => 'text',
		'DECIMAL' => 'text',
		'DATE' => 'text',
		'DATETIME' => 'text',
		'TIMESTAMP' => 'text',
		'TIME' => 'text',
		'ENUM' => 'radio',
		'ENUMERATION' => 'radio',
		'SET' => 'checkbox'
	);
	protected $table = null;
	public $show_fields = array(); // Array de campos a mostrar en el listado
	public $detail_show_fields = array(); // Array de campos a mostrar en el detalle (readonly)
	public $insert_fields = array(); // Array de campos permitidos para insertar nuevo registro
	public $update_fields = array(); // Array de campos permitidos para actualizar
	public $update_show_fields = array(); // Array de campos permitidos para actualizar
	public $update_extra_tables = array(); // Tablas extra para editar los campos
	public $field_types = array();
	public $field_names = array();


	function __construct ($table=null) {
		parent::__construct();

		if ($table)
			$this->table($table);
	}

	/**
	 * Devuelve una unica instancia de esta clase
	 * 
	 * @return object Instancia de esta clase
	 */
	public static function getInstance() {
		if (!self::$instancia instanceof self)
			self::$instancia = new self;
		return self::$instancia;
	}
	
	function exists ($tabla) {
		
		// Saneamiento de variables
		$tabla = addslashes($tabla);
		
		// Instancio el objeto de base de datos
		$db = ez_sql::getInstance();
		
		return (bool)$db->get_var("SELECT 1 a FROM $tabla LIMIT 1");
	}

	function html_tag ($field) {
		
		$return = array();
		
		// El Field queda solo con el tipo de dato, el resto lo pongo 
		$return['Type'] = strtolower(preg_replace('/^([^\( ]+).*/i', '\\1', $field['Type']));
		$length = preg_replace('/\'/i', '', preg_replace('/(.+?)\((.+?)\)(.*)/i', '\\2', $field['Type'], -1, $length_count));
		$return['Extrainfo'] = (($extrainfo = preg_replace('/(.+?) (.+?)/i', '\\2', $field['Type'], -1, $count)) && ($count > 0))? $extrainfo : '';

		// Si esta definido, lo tomo de ahi
		$return['Tag'] = ($this->field_types[$field['Field']])? $this->field_types[$field['Field']] : $this->default_field_types[strtoupper($return['Type'])];

		// Si es un primary key, lo defino como hidden
		if ($field['Key'] == 'PRI')
			$return['Tag'] = 'hidden';

		// Si tiene values, tambien los devuelvo
		if (in_array($return['Type'], array('enumeration', 'enum', 'set')))
			$return['Values'] = array_combine(explode(',', $length), explode(',', $length));
		// devuelvo el length del campo tambien
		else if ($length_count > 0)
			$return['Length'] = $length;

		// Me fijo si tengo que cargar este campo con informacion de otra tabla
		if ($this->update_extra_tables[$field['Field']]) {
			if (!self::exists($this->update_extra_tables[$field['Field']][0]))
				$return['Values'] = array_combine($this->update_extra_tables[$field['Field']], $this->update_extra_tables[$field['Field']]);
			else {
				// Cargo el modelo
				$extramodel = new Model($this->update_extra_tables[$field['Field']][0]);

				if ( $extravalues = $extramodel->browse(array($this->update_extra_tables[$field['Field']][1], $this->update_extra_tables[$field['Field']][2])) )
					foreach ($extravalues as $extravalue)
						$return['Values'][$extravalue[$this->update_extra_tables[$field['Field']][1]]] = $extravalue[$this->update_extra_tables[$field['Field']][2]];
			}
		}

		// Si es de tipo fecha, le agrego la clase calendar
		if (in_array($return['Type'], array('date', 'datetime', 'timestamp', 'time')))
			$return['css'] = 'calendar';

		$return['Name'] = ($this->field_names[$field['Field']])? $this->field_names[$field['Field']] : preg_replace('/_/i', ' ', $field['Field']);

		return $return;
	}

	function table ($table=null) {
		// Si me paso una tabla, la almaceno
		if ($table)
			$this->table = $table;

		// Si no tiene tabla, trato de sacarla del objeto instanciado
		if (!$this->table) {
			$backtrace = debug_backtrace(true);
			$this->table = strtolower(preg_replace('/^(.+?)(model)$/i', '\\1', get_class($backtrace[0]['object'])));
		}

		return $this->table;
	}

	function add ($data) {

		// Si no tengo ninguna tabla definida, aborto
		if (!$table = $this->table())
			return false;

		// Obtengo una instancia de base de datos
		$db = ez_sql::getInstance();
		
		// Si hay restriccion de campos de update, quito los que no esten permitidos
		if (!empty($this->insert_fields))
			foreach ($data as $k => $v)
				if (array_search($k, $this->insert_fields)===false)
					unset($data[$k]);

		return $db->insert($table, $data);
		
	}
	
	function where ($where=null) {
		if ($where)
			$this->where = $where;
		return $this->where;
	}

	function limit ($offset=null, $limit=null) {

		if ($limit!==null)
			$this->limit = $limit;
		if ($offset!==null)
			$this->offset = $offset;

		return array($this->limit, $this->offset);
	}

	function addFilter ($filter) {

		preg_match('/(.+?)(BETWEEN\:|:ISNOTNULL|:ISNULL|LIKE:|IN:|NOTIN:|\=|\>\=|\>|\<|\<\=|\:)(.*?)$/', $filter, $matches);

		// Almaceno el field
		$field = $matches[1];
		// Almaceno el operador
		$operator = $matches[2];
		// Almaceno el value
		$value = $matches[3];

		switch ($operator) {
				case 'BETWEEN:': 
					list($from,$to) = explode(",", $value);
					$condition = $field." BETWEEN '".$from." AND '".$to."' ";
				break;
				case ':ISNOTNULL': 
					$condition = ' IS NOT NULL('.$field.') ';
				break;
				case ':ISNULL': 
					$condition = ' IS NULL ('.$field.') ';
				break;
				case 'LIKE:': 
					$condition = $field." LIKE '".str_replace('@@', '%', $value)."'";
				break;
				case 'IN:': 
					$condition = $field." IN (".$value.") ";
				break;
				case 'NOTIN:': 
					$condition = $field." NOT IN (".$value.") ";
				break;
				default: 
					$condition = $field." = ".$value;
				break;
		}

		$this->filters[$field] = $condition;
	}

	function addGroup ($group) {
		$this->groups[] = $group;
	}

	function addGroups ($groups) {
		foreach ($groups as $group)
			$this->addGroup($group);
	}

	function groups () {
		return $this->groups;
	}

	function filters () {
		return $this->filters;
	}

	function addOrder ($field, $type="DESC") {
		$this->order[] = array($field, $type);
	}

	function order () {
		return $this->order;
	}

	function pk ($pk=null) {

		if ($pk)
			$this->pk = $pk;

		if (!$this->pk) {
			// Obtengo una instancia de base de datos
			$db = ez_sql::getInstance();
		
			$rows = $db->get_results("SHOW KEYS FROM ".$this->table()." WHERE key_name = 'PRIMARY'");

			if ($db->num_rows > 1)
				return false;
			$this->pk = $rows[0]['Column_name'];
		}

		return $this->pk;
	}

	function update ($data, $where=null) {

		// Si no tengo ninguna tabla definida, aborto
		if (!$table = $this->table())
			return false;

		// Obtengo una instancia de base de datos
		$db = ez_sql::getInstance();
		
		// Si hay restriccion de campos de update, quito los que no esten permitidos
		if (!empty($this->update_fields))
			foreach ($data as $k => $v)
				if (array_search($k, $this->update_fields)===false)
					unset($data[$k]);

		 $db->update($table, $data, $where);

	}

	function delete ($id) {

		// Si no tengo ninguna tabla definida, aborto
		if (!$table = $this->table())
			return false;

		// Obtengo una instancia de base de datos
		$db = ez_sql::getInstance();
		
		// Obtengo el primer campo de la tabla para usar como clave
		$campos = $db->get_results("SHOW fields FROM ".$table, 'OBJECT');
		$key = $campos[0]->Field;

		return $db->get_results("DELETE FROM ".$table." WHERE $key = '$id' LIMIT 1");
	}

	function structure () {

		// Obtengo una instancia de base de datos
		$db = ez_sql::getInstance();
		
		if (!$table = $this->table())
			return false;
			
		$fields = array();
		if ($structure = $db->get_results("SHOW FIELDS FROM $table"))
			foreach ($structure as $str)
//				$fields[$table.'.'.$str['Field']] = array_merge($str, $this->html_tag($str), array('Table' => $table));
				$fields[$str['Field']] = array_merge($str, $this->html_tag($str), array('Table' => $table));

		return $fields;
	}

	function browse ($_fields=null) {

		$_fields = ($_fields)? $_fields : $this->show_fields;

		$joins = array();
		$extra_fields = array();

		// Si no tengo ninguna tabla definida, aborto
		if (!$table = $this->table())
			return false;

		// Obtengo una instancia de base de datos
		$db = ez_sql::getInstance();
		
		if (!empty($_fields)) {
			foreach ($_fields as $k => $v)
				if (is_array($v)) {
					// Lo fuerzo a un array
					$vv = (is_array($v[0]))? $v : array($v);
					foreach ($vv as $sub_v) {
						// Obtengo la tabla
						$joins[preg_replace('/([^\.]+).*/i', '\\1', $sub_v[1])] = array('pk' => $sub_v[1], 'fk' => $k);
						$fields[] = preg_replace('/([^\.]+).*/i', '\\1', $sub_v[1]).'.'.$sub_v[0].' `'.preg_replace('/([^\.]+).*/i', '\\1', $sub_v[1]).'.'.$sub_v[0].'`';
					}
				} else {
					$fields[] = (!preg_match('/.+?\..+?/', $v))? $table.'.'.$v.' `'.$table.'.'.$v.'` ' : $v.' `'.$v.'` ';
				}
			$fields_str = implode(",", $fields);
		}
		else {
			$fields_str = "$table.*";
		}

		// Armo los joins
		$joins_str = '';
		if (!empty($joins))
			foreach ($joins as $k => $v)
				$joins_str .= " LEFT JOIN $k ON $table.".$v['fk']." = ".$v['pk']." ";

		$where = ($this->where())? $this->where() : '';
		
		// Obtengo el primary_key
		$primary_key = $this->pk();

		// Obtengo el limit
		$limit = $this->limit();

		// Obtengo los order by
		$order_by = '';
		if (($orders = $this->order()) && ($order_by = ' ORDER BY ') ) {
			foreach ($orders as $order)
				$order_by .= $order[0].' '.$order[1].',';
			$order_by = substr($order_by, 0, -1);
		}

		// Obtengo los group by
		$group_by = '';
		if ($groups = $this->groups())
			$group_by = ' GROUP BY '.implode(',', $groups);

		// Obtengo los filters
		if ($filers = $this->filters())
			foreach ($filers as $filter_key => $filter_condition)
				$where .= ' AND '.$filter_condition.' ';

		// Ejecuto el query
		$_res = $db->get_results("SELECT ".(($primary_key)? $table.'.'.$primary_key." primary_key, " : '')."$fields_str FROM ".$table." $joins_str WHERE 1=1 $where $group_by $order_by LIMIT ".($limit[0]*($limit[1]-1)).', '.$limit[0]);

		// Elimino el nombre de la tabla en el nombre del campo para este modelo
		$_res = unserialize(preg_replace('/;s:([0-9]+):\"'.$table.'\./ie', "';s:'.('\\1'-(strlen($table)+1)).':\"'", serialize($_res)));

//$db->debug();
		$res = array();
		if ($primary_key)
			foreach ($_res as $row) {
				$primary_key = $row['primary_key'];
				unset($row['primary_key']);
				$res[$primary_key] = $row;
			}
		else
			$res = $_res;

//		$return['Name'] = ($this->field_names[$field['Field']])? $this->field_names[$field['Field']] : preg_replace('/_/i', ' ', $field['Field']);
		return $res;
	}
	

	function get ($id, $fields=null) {

		// Obtengo el pk de la tabla
		$pk = $this->pk();
		// Defino el where
		$this->where(" AND $pk = '$id' ");
		// Llamo al listado con el where seteado
		$row = current($this->browse($fields));
		// Devuelvo el primer resultado
		return $row;
	}


}
//http://local.default/products/browse/order:SupplierID,DESC/filter:CategoryID:2/limit:10,0
?>
